package com.yyfax.pay.depository.service;

import java.util.Date;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.yyfax.commons.lang.DateUtil;
import com.yyfax.framework.common.Log;
import com.yyfax.framework.common.OpResponse;
import com.yyfax.framework.yyfax.CryptUtil;
import com.yyfax.pay.common.enums.FuiouBusiTypeEnum;
import com.yyfax.pay.common.enums.FuiouDepositRspCodeEnum;
import com.yyfax.pay.common.enums.FuiouDepositStateEnum;
import com.yyfax.pay.common.enums.PaymentEnum;
import com.yyfax.pay.common.exception.PayException;
import com.yyfax.pay.common.exception.PayExceptionCode;
import com.yyfax.pay.common.model.vo.PayRspCodeVO;
import com.yyfax.pay.depository.dao.FuiouChargeDao;
import com.yyfax.pay.depository.domain.FuiouCharge;
import com.yyfax.pay.depository.service.inner.ChargeInnerService;
import com.yyfax.pay.depository.service.inner.FuiouDepositCommonService;
import com.yyfax.pay.depository.vo.FuiouChargeVO;
import com.yyfax.pay.depository.vo.FuiouResponseVO;
import com.yyfax.pay.depository.vo.QuickChargePayVO;
import com.yyfax.pay.depository.vo.QuickChargeSendSmsVO;
import com.yyfax.pay.order.data.service.UserPaymentService;
import com.yyfax.pay.service.depository.ChargeService;
import com.yyfax.pay.third.facade.fuioudeposit.FuiouDepositEntrustService;
import com.yyfax.pay.third.facade.fuioudeposit.FuiouDepositService;
import com.yyfax.pay.third.facade.fuioudeposit.FuiouSignService;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouDepositConfigExt;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.BasePlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.RootXMLRsp;
import com.yyfax.pay.vo.depository.ChargeBaseReq;
import com.yyfax.pay.vo.depository.EntrustChargeReq;
import com.yyfax.pay.vo.depository.QuickChargePayReq;
import com.yyfax.pay.vo.depository.RspContent;

@Service
public class ChargeServiceImpl implements ChargeService {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	@Resource
	private ChargeInnerService chargeInnerService;

	@Resource
	private FuiouDepositService fuiouDepositService;
	@Resource
	private FuiouChargeDao fuiouChargeDao;
	@Resource
	private FuiouSignService fuiouSignService;
	@Resource
	private FuiouDepositCommonService fuiouDepositCommonService;
	@Resource
	private FuiouDepositEntrustService fuiouDepositEntrustService;
	@Resource
	private UserPaymentService userPaymentService;

	@Override
	public void quickChargeSendSms(String merchantCode, ChargeBaseReq params) throws Exception {
		FuiouCharge po = fuiouChargeDao.selectByMerchantCodeAndOrderId(merchantCode, params.getOrderId());
		if (po == null) {
			// 保存订单
			FuiouChargeVO vo = chargeInnerService.saveCharge(merchantCode, params);
			po = vo.getPo();
		}

		// 成功直接返回, 其余抛出异常
		if (FuiouDepositStateEnum.SUCCESS.isValue(po.getState())) {
			return;
		} else if (FuiouDepositStateEnum.ACCEPTED.isValue(po.getState()) || FuiouDepositStateEnum.FAIL.isValue(po.getState())) {
			throw new PayException(po.getRspCode(), po.getRspMsg());
		}

		FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());
		// 构建请求参数
		QuickChargeSendSmsVO quickChargeSendSmsVO = buildReqParams(po, extInfo);

		// 请求存管系统
		FuiouResponseVO resp = fuiouDepositService.postForResponseVO(quickChargeSendSmsVO, extInfo.getQuickChargeSendSmsUrl(), merchantCode);

		// 处理返回结果
		handleSendSmsResponse(resp, po);
	}

	/**
	 * 处理快捷充值发送验证码结果
	 *
	 * @author yejc
	 * @date 2017-09-30
	 * @since v2.0.0
	 * @param resp
	 * @param po
	 */
	private void handleSendSmsResponse(FuiouResponseVO resp, FuiouCharge po) {
		final String op = "ChargeServiceImpl.handleSendSmsResponse";
		if (FuiouResponseVO.INVALID_DATA.equals(resp.getResult())) {
			fuiouChargeDao.updateState(po.getBusiId(), FuiouDepositStateEnum.FAIL.getValue(), FuiouDepositStateEnum.INI.getValue(),
					PayExceptionCode.ERROR_SIGN_FAIL.getCode(), "返回数据验签失败或数据异常", DateUtil.getCurrentDateTime());
			throw PayExceptionCode.ERROR_PAY_FAIL.expMsg("返回数据验签失败或数据异常");
		} else if (FuiouResponseVO.TIMEOUT.equals(resp.getResult())) {
			throw PayExceptionCode.WARN_REQ_TIME_OUT.exp();
		}

		String result = resp.getContent();
		RootXMLRsp<BasePlain> rsp = (RootXMLRsp<BasePlain>) RootXMLRsp.fromXml(result, BasePlain.class);
		if (FuiouDepositRspCodeEnum.VERIFY_CODE_OVER_LIMIT.getValue().equals(rsp.getPlain().getRspCode())) {
			// 验证码发送次数超限的话，要返回一个固定的错误码让业务特殊处理
			fuiouChargeDao.updateState(po.getBusiId(), FuiouDepositStateEnum.FAIL.getValue(), FuiouDepositStateEnum.INI.getValue(), PayExceptionCode.VERIFY_CODE_OVER_LIMIT.getCode(), rsp.getPlain().getRspMsg(), DateUtil.getCurrentDateTime());
			throw PayExceptionCode.VERIFY_CODE_OVER_LIMIT.exp();
		}
		if (FuiouDepositRspCodeEnum.ACCEPTED.getValue().equals(rsp.getPlain().getRspCode())) {
			// 验证码三分钟内频繁请求，富友会返回0001
			throw new PayException(PayExceptionCode.FAIL_TIP_MSG.getCode(), "请求频繁,请稍后");
		}
		if (!FuiouDepositRspCodeEnum.SUCCESS.getValue().equals(rsp.getPlain().getRspCode())) {
			// 更新订单处理状态为失败
			PayRspCodeVO expCode = fuiouDepositService.converToPayRspCode(rsp.getPlain().getRspCode());
			logger.warn(Log.op(op).msg("快捷充值发送验证码失败").kv("响应码", rsp.getPlain().getRspCode()).kv("响应描述", rsp.getPlain().getRspMsg()).kv("busiId", po.getBusiId()).kv("orderId", po.getOrderId()).toString());
			fuiouChargeDao.updateState(po.getBusiId(), FuiouDepositStateEnum.FAIL.getValue(), FuiouDepositStateEnum.INI.getValue(), expCode.getCode(), expCode.getDesc(), DateUtil.getCurrentDateTime());
			throw new PayException(expCode.getCode(), expCode.getDesc());
		}
	}

	private QuickChargeSendSmsVO buildReqParams(FuiouCharge order, FuiouDepositConfigExt extInfo) throws Exception {
		QuickChargeSendSmsVO req = new QuickChargeSendSmsVO();
		req.setMerchantId(extInfo.getMerchantId()); // 商户id
		req.setBusiId(order.getBusiId());
		req.setAccountNo(order.getLoginId());
		req.setAmount(order.getAmount().toString());
		req.setPhone(order.getPhone() == null ? "" : order.getPhone());
		String cardNo = CryptUtil.decrypt(order.getCardNoCip());
		req.setBankCardNo(cardNo == null ? "" : cardNo);
		req.setSignature(fuiouSignService.getSign(order.getMerchantCode(), req));
		return req;
	}

	@Override
	public OpResponse quickCharge(QuickChargePayReq params, String merchantCode) throws Exception {
		FuiouCharge order = fuiouChargeDao.selectByMerchantCodeAndOrderId(merchantCode, params.getOrderId());
		if (order == null) {
			throw PayExceptionCode.ERROR_ILL_PARAMETER.expMsg("无法查询到相应的订单，请重新获取短信验证码");
		}

		// 更新订单
		int i = fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.DEALING.getValue(), FuiouDepositStateEnum.INI.getValue(), PayExceptionCode.PROCESSING.getCode(), PayExceptionCode.PROCESSING.getDesc(), DateUtil.getCurrentDateTime());
		if (i > 0) {
			FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(merchantCode);
			// 构建请求参数
			QuickChargePayVO quickChargeVO = buildReqParams(order, extInfo, params.getVerificationCode());

			// 请求存管系统
			FuiouResponseVO resp = fuiouDepositService.postForResponseVO(quickChargeVO, extInfo.getQuickChargeUrl(), merchantCode);

			// 处理返回结果
			handleQuickChargeResponse(resp, order);
		}
		// 根据订单最终的状态, 返回响应
		FuiouCharge charge = fuiouChargeDao.selectByBusiId(order.getBusiId());
		OpResponse response = new OpResponse();
		RspContent rspContent = new RspContent();
		rspContent.setBusiId(charge.getBusiId());
		response.setCode(charge.getRspCode());
		response.setDisplay(charge.getRspMsg());
		response.setContent(rspContent);

		return response;
	}

	@Override
	public OpResponse entrustCharge(EntrustChargeReq req, String merchantCode) throws Exception {
		FuiouChargeVO fuiouChargeVO = chargeInnerService.saveCharge(merchantCode, req);
		FuiouCharge order = fuiouChargeVO.getPo();

		int i = fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.DEALING.getValue(), FuiouDepositStateEnum.INI.getValue(), PayExceptionCode.PROCESSING.getCode(), PayExceptionCode.PROCESSING.getDesc(), DateUtil.getCurrentDateTime());
		if (i > 0) {
			PayRspCodeVO payRspCodeVO = fuiouDepositEntrustService.entrustCharge(order);

			// 处理返回结果
			handleEntrustChargeResponse(payRspCodeVO, order);
		}

		FuiouCharge charge = fuiouChargeDao.selectByBusiId(order.getBusiId());
		OpResponse response = new OpResponse();
		RspContent rspContent = new RspContent();
		if (charge.getState().equals(FuiouDepositStateEnum.ACCEPTED.getValue())) {
			rspContent.setState(RspContent.PROCESSING);
		} else if (charge.getState().equals(FuiouDepositStateEnum.SUCCESS.getValue())) {
			rspContent.setState(RspContent.SUCCESS);
		}
		rspContent.setBusiId(charge.getBusiId());
		response.setCode(charge.getRspCode());
		response.setDisplay(charge.getRspMsg());
		response.setContent(rspContent);

		return response;
	}

	/**
	 * 处理委托充值结果
	 *
	 * @author yejc
	 * @date 2017-09-30
	 * @since v2.0.0
	 * @param resp
	 * @param order
	 */
	private void handleEntrustChargeResponse(PayRspCodeVO resp, FuiouCharge order) {
		final String op = "ChargeServiceImpl.handleEntrustChargeResponse";
		// 超时
		if (PayExceptionCode.WARN_REQ_TIME_OUT.isCode(resp.getCode())) {
			logger.warn(Log.op(op).msg("委托充值请求超时").kv("busiId", order.getBusiId()).toString());
			fuiouDepositCommonService.saveQueryTask(order.getMerchantCode(), order.getBusiId(), FuiouBusiTypeEnum.CHARGE_QUERY);
			throw PayExceptionCode.WARN_REQ_TIME_OUT.exp();
		}

		if (PayExceptionCode.SUCCESS.isCode(resp.getCode())) {
			// 更新订单处理状态为已受理，还要等待back_notify
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.ACCEPTED.getValue(),  FuiouDepositStateEnum.DEALING.getValue(), resp.getCode(), resp.getDesc(), DateUtil.getCurrentDateTime());
		} else if (PayExceptionCode.isToConfirm(resp.getCode())) {
			//待确认更新订单状态为已受理,等待back_notify
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.ACCEPTED.getValue(), FuiouDepositStateEnum.DEALING.getValue(), resp.getCode(), resp.getDesc(), DateUtil.getCurrentDateTime());
		} else {
			// 更新订单处理状态为失败
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.FAIL.getValue(),  FuiouDepositStateEnum.DEALING.getValue(), resp.getCode(), resp.getDesc(), DateUtil.getCurrentDateTime());
		}
	}

	/**
	 * 处理快捷充值快捷支付结果
	 *
	 * @author yejc
	 * @date 2017-09-30
	 * @since v2.0.0
	 * @param resp
	 * @param order
	 */
	private void handleQuickChargeResponse(FuiouResponseVO resp, FuiouCharge order) {
		final String op = "ChargeServiceImpl.handleQuickChargeResponse";
		// 响应数据异常或接口请求超时
		if (FuiouResponseVO.INVALID_DATA.equals(resp.getResult())) {
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.FAIL.getValue(), FuiouDepositStateEnum.DEALING.getValue(), PayExceptionCode.SYS_ERROR.getCode(), PayExceptionCode.SYS_ERROR.getDesc(), DateUtil.getCurrentDateTime());
			return;
		} else if (FuiouResponseVO.TIMEOUT.equals(resp.getResult())) {
			logger.warn(Log.op(op).msg("快捷充值请求超时").kv("busiId", order.getBusiId()).toString());
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.DEALING.getValue(), FuiouDepositStateEnum.DEALING.getValue(), PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc(), DateUtil.getCurrentDateTime());
			fuiouDepositCommonService.saveQueryTask(order.getMerchantCode(), order.getBusiId(), FuiouBusiTypeEnum.CHARGE_QUERY);
			return;
		}

		String result = resp.getContent();
		RootXMLRsp<BasePlain> rsp = (RootXMLRsp<BasePlain>) RootXMLRsp.fromXml(result, BasePlain.class);

		// 验证码错误不能把订单改为失败
		// 验证码失效或错误把订单恢复为初始状态，以便调用方重新输入正确的验证码再次请求富友
		if (FuiouDepositRspCodeEnum.VERIFY_CODE_INVALID.getValue().equals(rsp.getPlain().getRspCode())) {
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.INI.getValue(), FuiouDepositStateEnum.DEALING.getValue(), PayExceptionCode.VERIFY_CODE_INVALID.getCode(), PayExceptionCode.VERIFY_CODE_INVALID.getDesc(), DateUtil.getCurrentDateTime());
			return;
		}

		PayRspCodeVO expCode = fuiouDepositService.converToPayRspCode(rsp.getPlain().getRspCode());
		if (PayExceptionCode.WARN_REQ_TIME_OUT.isCode(expCode.getCode()) || PayExceptionCode.TO_CONFIRM.isCode(expCode.getCode())) {
			// 富友响应码返回超时或待确认
			logger.warn(Log.op(op).msg("快捷充值请求返回未知状态，超时或待确认").kv("响应码", rsp.getPlain().getRspCode()).kv("响应描述", rsp.getPlain().getRspMsg()).kv("busiId", order.getBusiId()).kv("orderId", order.getOrderId()).toString());
			fuiouDepositCommonService.saveQueryTask(order.getMerchantCode(), order.getBusiId(), FuiouBusiTypeEnum.CHARGE_QUERY);
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.DEALING.getValue(), FuiouDepositStateEnum.DEALING.getValue(), expCode.getCode(), expCode.getDesc(), DateUtil.getCurrentDateTime());
			return;
		}

		if (!PayExceptionCode.SUCCESS.isCode(expCode.getCode())) {
			//  其他情况把订单处理状态改为失败
			logger.warn(Log.op(op).msg("快捷充值快捷支付失败").kv("响应码", rsp.getPlain().getRspCode()).kv("响应描述", rsp.getPlain().getRspMsg()).kv("busiId", order.getBusiId()).kv("orderId", order.getOrderId()).toString());
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.FAIL.getValue(), FuiouDepositStateEnum.DEALING.getValue(), expCode.getCode(), expCode.getDesc(), DateUtil.getCurrentDateTime());
		} else {
			// 更新订单处理状态改为成功
			fuiouChargeDao.updateState(order.getBusiId(), FuiouDepositStateEnum.SUCCESS.getValue(), FuiouDepositStateEnum.DEALING.getValue(), PayExceptionCode.SUCCESS.getCode(), rsp.getPlain().getRspMsg(), DateUtil.getCurrentDateTime());
			// 快捷充值成功，更新user_payment的银行预留手机号
			if (StringUtils.isNotBlank(order.getPhone())) {
				userPaymentService.modifyPhone(order.getMerchantCode(), order.getUserId(), PaymentEnum.FUIOU_DEPOSIT.getValue(), order.getPhone());
			}
		}
	}

	private QuickChargePayVO buildReqParams(FuiouCharge order, FuiouDepositConfigExt extInfo, String verificationCode) {
		QuickChargePayVO vo = new QuickChargePayVO();
		vo.setMerchantId(extInfo.getMerchantId());
		vo.setBusiId(order.getBusiId());
		vo.setTradeDate(DateUtil.date2Str(new Date(), "yyyyMMdd"));
		vo.setVerificationCode(verificationCode);
		vo.setPhone(order.getPhone() == null ? "" : order.getPhone());
		vo.setSignature(fuiouSignService.getSign(order.getMerchantCode(), vo));
		return vo;
	}

}
